package com.tools.web.websocket;

import com.tools.common.container.ConcurrentHashSet;
import com.tools.common.container.OwnRows;
import com.tools.common.exception.IORuntimeException;
import com.tools.common.exception.NetOperationException;
import com.tools.common.object.CustomHandler;
import com.tools.common.object.Note;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;


@Note("1.在 @Configuration 里配置：SpringBeanKit.serverEndpointExporter() 实例进 IOC 容器。" +
        "2.WebSocket 的核心类。" +
        "里面已经写死了接口路径，采用 RestFUL 风格的 URI 地址。" +
        "注意该类需要被加入 Spring 容器内，需要到时在方法返回值那里加 @Bean 注解")
@ServerEndpoint("/websocket/{userId}")
public abstract class WebSocketBean extends CustomHandler {

    @Note("与某个客户端的连接会话，需要通过它来给客户端发送数据")
    private Session session;

    @Note("客户端用户 ID，一般要用做 RESTFUL 接口的关键字")
    private String userId;

    @Note("存放每个客户端对应的 WebSocketBean 对象" +
            "SpringBoot 还是会为每个 WebSocket 连接初始化一个 Bean，所以可以用一个静态线程安全的 Set 保存起来")
    private static ConcurrentHashSet<WebSocketBean> SOCKETS;

    @Note("用来存储在线连接用户信息。Key 是客户端用户 ID， Value 是与某个客户端的连接会话，需要通过它来给客户端发送数据。")
    private static ConcurrentHashMap<String, Session> SESSIONS;

    @Note("日志输出对象")
    private static Logger LOGGER;

    /* ************************************************************************************************************
     *
     *
     *          构造器、Getter
     *
     * ************************************************************************************************************
     * */

    public WebSocketBean() {
        super();
        private_initContainer();
    }

    public WebSocketBean(Object param) {
        super(param);
        private_initContainer();
    }

    public WebSocketBean(Object... params) {
        super(params);
        private_initContainer();
    }

    public WebSocketBean(Iterable<?> params) {
        super(params);
        private_initContainer();
    }

    protected Session getSession() {
        return session;
    }

    protected String getUserId() {
        return userId;
    }

    /* ************************************************************************************************************
     *
     *      关键方法
     *
     * ************************************************************************************************************
     * */

    @Note("成功建立连接后调用的方法")
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        this.session = session;
        this.userId = userId;
        SOCKETS.add(this);
        SESSIONS.put(userId, session);
        LOGGER.info("【WebSocket 消息】有新的连接: {}，当前连接总数为: {}", userId, SOCKETS.size());
    }


    @Note("连接关闭后调用的方法")
    @OnClose
    public void onClose() {
        try {
            SOCKETS.remove(this);
            Session session = SESSIONS.remove(this.userId);
            session.close();
            LOGGER.info("【WebSocket 消息】 {} 连接已断开，当前连接总数为: {}", userId, SOCKETS.size());
        } catch (IOException e) {
            LOGGER.info("【WebSocket 消息】 {} 连接断开失败，当前连接总数为: {}", userId, SOCKETS.size());
            throw new IORuntimeException(e);
        }
    }

    @Note("发送错误时的处理")
    @OnError
    public void onError(Session session, Throwable error) {
        LOGGER.error("session: {} 发送数据错误，原因是: {}", session.getId(), error.getMessage());
        error.printStackTrace();
    }

    @Note("收到客户端的消息后调用的方法。自定义实现")
    @OnMessage
    public abstract void onMessage(String message);


    @Note("向某个客户端单独发送消息")
    public void sendOneMessage(String message, String userId) {
        private_sendText(message, userId);
    }


    @Note("向多个客户端单独发送消息")
    public void sendOneMessage( String message, String... userIds) {
        if(userIds == null || userIds.length == 0) throw new IllegalArgumentException("至少需要一个可以发送的客户端");
        for (String userId : userIds) private_sendText(userId, message);
    }

    @Note("向多个客户端单独发送消息")
    public void sendOneMessage( String message, Collection<String> userIds) {
        if(userIds == null || userIds.isEmpty()) throw new IllegalArgumentException("至少需要一个可以发送的客户端");
        for (String userId : userIds) private_sendText(userId, message);
    }

    @Note("向多个客户端单独发送消息")
    public void sendOneMessage( String message, OwnRows<String> userIds) {
        if(userIds == null || userIds.isEmpty()) throw new IllegalArgumentException("至少需要一个可以发送的客户端");
        for (String userId : userIds) private_sendText(userId, message);
    }

    @Note("向多个客户端单独发送消息")
    public void sendOneMessage(String message, Iterable<String> userIds) {
        if(userIds == null) throw new IllegalArgumentException("至少需要一个可以发送的客户端");
        Iterator<String> iterator = userIds.iterator();
        if(!iterator.hasNext()) throw new IllegalArgumentException("至少需要一个可以发送的客户端");
        while (iterator.hasNext()){
            private_sendText(iterator.next(), message);
        }
    }

    @Note("广播消息")
    public void sendAllUser(String message) {
        for(WebSocketBean webSocket : SOCKETS) {
            if(webSocket.session.isOpen()) {
                webSocket.session.getAsyncRemote().sendText(message);
            }
        }
    }


    /* ************************************************************************************************************
     *
     *
     *          私有逻辑
     *
     * ************************************************************************************************************
     * */

    @Note("懒加载初始化并发安全的容器存储 WebSocket 的各种数据")
    private static void private_initContainer() {
        if(SOCKETS != null) return;
        synchronized (WebSocketBean.class) {
            if(SOCKETS != null) return;
            SOCKETS = new ConcurrentHashSet<>();
            SESSIONS = new ConcurrentHashMap<>();
            LOGGER = LoggerFactory.getLogger(WebSocketBean.class);
        }
    }


    @Note("单个发送消息的逻辑")
    private static void private_sendText(String userId, String message) {
        Session session = SESSIONS.get(userId);
        if(session == null) {
            throw new NetOperationException(userId + " 该用户尚未连接");
        }
        session.getAsyncRemote().sendText(message);
    }
}
